package com.cneport.das.client.trans.file;

import cn.hutool.json.JSONUtil;
import com.cneport.das.client.conf.ClientConfCache;
import com.cneport.das.client.conf.FileConf;
import com.cneport.das.common.model.trans.ErpPutRequest;
import com.cneport.das.common.model.trans.ErpPutResponse;
import com.cneport.das.common.model.trans.ReceiverIds;
import com.cneport.das.common.utils.*;
import com.cneport.das.common.validate.FieldCheckUtil;
import com.cneport.das.common.validate.ResultVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.*;

/**
 * @Desc 企业系统与DAS client 以文件方式交互 （报文文件处理）
 * 目录结构 dest/corpNo/C8(yyyyMMdd)/guid.xml或guid.json
 * 处理出错的文件，最终转移到dest/corpNo/error/C8的目录下，C8同原来的C8
 * 处理失败，若是报文格式的问题，直接失败，若是其它问题，尝试3（可配）次，每次扫描启动都优先尝试处理之前的失败的，若最终还是失败，则转入上述error目录。
 * 失败的文件格式：guid.1.xml 表示第一次失败。guid.2.xml 表示第二次失败 ... 失败的原因都记入/dest/corpNo/error.log。
 * 处理成功的文件，将直接删除，若C8目录下为空,将删除C8.
 * @Ver 1.0
 * @Author GavinZhang
 * @Company 东方物通科技有限（北京）公司
 * @Date 2021-12-29 10:58:42
 */
@Component
@Slf4j
public class FileParser {
    @Autowired
    private ClientConfCache clientConfCache;

    private static final Long Sec2Mills = 1000L;

    private static final Map<String, Long> nextTriger = new ConcurrentHashMap();

    private final static ExecutorService pool = new ThreadPoolExecutor(2, 3, 90, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

    /**
     * 读取文件
     */
    @Scheduled(initialDelay = 1000L, fixedRate = 60 * 1000L)
    public void deal() {
        log.info("开始尝试处理报文文件！");
        Set<String> cops = clientConfCache.getAllCorpNo();
        if (cops != null && cops.size() > 0) {
            for (String corpNo : cops) {
                if (StringUtil.isBlank(corpNo)) {
                    log.error("fileExchange.xml 配置文件中存在 corpNo为空，请检查！");
                    continue;
                }
                FileConf dasClient = clientConfCache.getFileConfigByCorpNo(corpNo);
                if (nextTriger.get(corpNo) == null || nextTriger.get(corpNo) <= System.currentTimeMillis()) {
                    //处理每一个公司
                    Action action = new Action(corpNo);
                    pool.submit(action);
                    Long fre = dasClient.getFileUp().getFreq();
                    nextTriger.put(corpNo, System.currentTimeMillis() + fre * Sec2Mills);
                }
            }
        }
    }

    private class Action implements Runnable {
        private String copNo;
        private String filepath;

        public Action(String corpNo) {
            copNo = corpNo;
        }

        @Override
        public void run() {
            final String error = "error";
            //读取文件
            FileConf dasClient = clientConfCache.getFileConfigByCorpNo(copNo);
            if (dasClient == null) {
                return;
            }
            FileConf.FileUp fileUp = dasClient.getFileUp();
            if (fileUp == null) {
                return;
            }
            String dest = dasClient.getFileUp().getDest();
            String format = fileUp.getFormat();
            if (StringUtil.isBlank(format)) {
                format = "xml";
            }
            filepath = dest + File.separator + copNo;
            File baseDir = new File(filepath);
            if (!baseDir.exists()) {
                return;
            }
            String[] dates = baseDir.list();
            if (dates != null && dates.length > 0) {
                for (String dt : dates) {
                    String dateDir = filepath + File.separator + dt;
                    File file = new File(dateDir);
                    if (file.isFile() || error.equals(dt)) {
                        continue;
                    }
                    String[] fileanmes = file.list();
                    if (fileanmes != null && fileanmes.length > 0) {
                        for (String fn : fileanmes) {
                            String fp = dateDir + File.separator + fn;
                            String content = FileUtil.readFile(fp, StandardCharsets.UTF_8.toString());
                            if (!StringUtil.isBlank(content)) {
                                String errorDir = filepath + File.separator + error + File.separator + dt;
                                //处理报文
                                doAction(content, fp, format, errorDir);
                            }
                        }
                    } else {
                        //没有子目录，删除
                        file.delete();
                    }
                }
            }
        }

        /**
         * 处理报文
         *
         * @param content
         * @param fileName
         * @param format
         */
        private void doAction(String content, String fileName, String format, String errorDir) {
            final String JSON = "json";
            final String XML = "xml";

            ErpPutRequest erpPutRequest = null;
            if (JSON.equals(format.trim().toLowerCase(Locale.ROOT))) {
                try {
                    erpPutRequest = JSONUtil.toBean(content, ErpPutRequest.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if (XML.equals(format.trim().toLowerCase(Locale.ROOT))) {
                erpPutRequest = JAXBUtils.xml2Obj(ErpPutRequest.class, content);
            }
            if (erpPutRequest != null) {
                //校验头
                ErpPutRequest.ReqHead reqHead = erpPutRequest.getReqHead();
                ResultVO resultVO = FieldCheckUtil.validate(reqHead, null);
                //校验收件方列表
                ReceiverIds receiverIds = reqHead.getReceiverIds();
                if (receiverIds == null || receiverIds.getReceiverId().size() == 0) {
                    String err = "没有收件方！";
                    log.error(err);
                    fastFail(fileName, err, errorDir);
                    return;
                }
                if (resultVO.isSuccess()) {
                    //处理
                    String xml = JAXBUtils.obj2Xml(erpPutRequest);
                    String result = callClient(xml);
                    //处理失败
                    if (!StringUtil.isBlank(result)) {
                        fail(fileName, result, errorDir);
                    } else {
                        //处理成功了，删除原报文。
                        File file = new File(fileName);
                        file.delete();

                    }
                } else {
                    String err = resultVO.getErrorStr();
                    log.error("报文格式校验不通过：" + err);
                    fastFail(fileName, err, errorDir);
                }

            } else {
                String error = "报文内容不符合规范，无法转成对象";
                log.error(error);
                fastFail(fileName, error, errorDir);
            }
        }

        /**
         * 失败时的文件处理及日志写入
         *
         * @param fileName 全路径的文件名
         * @param errMsg   错误信息
         * @param errorDir 错误报文的目录
         */
        private void fail(String fileName, String errMsg, String errorDir) {
            final String ERROR_FILE = "error.log";
            File file = new File(fileName);
            String sourceFileName = file.getName();
            String fileName_no_ext = FileUtil.getFileNameWithoutExt(file);
            String fileExt = FileUtil.getFileExtName(file);
            final int attempt = 3;
            //分析文件名，是否是以.n结尾
            int idx = fileName_no_ext.indexOf(".");
            String newFile = "";
            if (idx != -1) {
                String baseName = fileName_no_ext.substring(0, fileName_no_ext.length() - 2);
                int i = -1;
                try {
                    i = Integer.parseInt(fileName_no_ext.charAt(fileName_no_ext.length() - 1) + "");
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (i == -1) {
                    log.error("文件名不符合规范：" + fileName_no_ext);
                    return;
                }
                //少于允许错误次数
                if (i < attempt) {
                    i = i + 1;
                    newFile = baseName + "." + i;
                    file.renameTo(new File(file.getParentFile().getPath() + File.separator + newFile + "." + fileExt));
                } else {
                    File efl = new File(errorDir);
                    if (!efl.exists()) {
                        efl.mkdirs();
                    }
                    //把报文文件移入错误目录
                    file.renameTo(new File(errorDir + File.separator + file.getName()));
                }
            } else {
                fileName = fileName_no_ext + ".1" + "." + fileExt;
                file.renameTo(new File(file.getParentFile().getPath() + File.separator + fileName));
            }
            //记日志
            writeLog(file.getParentFile().getName() + File.separator + sourceFileName, errMsg);
        }

        /**
         * 处理报文文件立即失败
         *
         * @param fileName 全路径的文件名
         * @param errMsg   错误信息
         */
        private void fastFail(String fileName, String errMsg, String errorDir) {

            File file = new File(fileName);
            String sourceFileName = file.getName();
            File efl = new File(errorDir);
            if (!efl.exists()) {
                efl.mkdirs();
            }
            String newPath = errorDir + File.separator + sourceFileName;
            //移动
            File fl = new File(newPath);
            if (fl.exists()) {
                fl.delete();
            }
            boolean b = file.renameTo(new File(newPath));
            //记日志
            writeLog(file.getParentFile().getName() + File.separator + sourceFileName, errMsg);
        }

        /**
         * 记日志
         *
         * @param fileName 不带全路径的文件名称
         * @param msg      错误信息
         */
        private void writeLog(String fileName, String msg) {
            final String ERROR_FILE = "error.log";
            FileUtil.appendLineToFile(DateUtil.now() + " ======> 处理文件【" + fileName + "】发生了异常：" + msg + "\n", filepath + File.separator + ERROR_FILE);

        }

        /**
         * 请求client
         *
         * @param msg
         */
        private String callClient(String msg) {
            msg = msg.trim();
            String code = "";
            String url = clientConfCache.getClientServiceUrl() + "/putClientMsg";
            String response;
            try {
                response = HttpUtils.post(url, msg, new HashMap<>());
                log.info("client --> ent response: \n" + response);
                ErpPutResponse erpPutResponse = JAXBUtils.xml2Obj(ErpPutResponse.class, response);
                ErpPutResponse.RespHead respHead = erpPutResponse.getRespHead();
                if (respHead.getRespCode().equals("-1")) {
                    String err = respHead.getRespMsg();
                    log.error("调用DAS Client 服务时发生异常：" + err);
                    code = err;
                }

            } catch (Exception e) {
                e.printStackTrace();
                String em = e.getLocalizedMessage();
                log.error("调用DAS Client 服务时发生异常：" + em);
                code = em;
            }
            return code;
        }
    }
}
